import {connect, JSONCodec, StringCodec , Events , DebugEvents} from "nats.ws";
import {Message} from 'element-ui'
import Error_resolver from "@/utils/error_resolver.js";

class Nats_client {
    constructor() {
        this.nc = null;
        this.subCache = new Map();
        this.subscriptions = new Map();
        this.tempCacheSubs = new Map();
        this.sc = new StringCodec();
        this.codec = new JSONCodec();
        this.errResolver = new Error_resolver();
        this.state = Events.Disconnect;
        this.connectionListeners = [];
    }

    conn = async (config) => {
        try {
            this.nc = await connect({
                servers: [`nats://${config.host}:${config.port}`],
                timeout: 3000,
                reconnect: true,
                maxReconnectAttempts: -1,
                pingInterval: 1000 * 5,
                name: config.name,
                reconnectTimeWait: 3000,
                waitOnFirstConnect: false,
                noAsyncTraces: false,
                token: config.token,
                user: config.username,
                pass: config.password,
                noEcho: config.noEcho,
            });
            if (this.nc && this.nc instanceof Error) {
                return this.nc;
            } else {

                this.state = DebugEvents.PingTimer;
                this.tempCacheSubs.forEach((fun, topic) => {
                    this.sub(topic, fun);
                });
                this.tempCacheSubs.clear();

                (async () => {
                    for await (const event of this.nc.status()) {
                        this.state = event.type;
                        this.connectionListeners.forEach(fn =>  fn(event));
                    }
                })().then();

                return true
            }
        } catch (error) {
            console.log(error)
            return false;
        }
    }

    sub = (topic, fun) => {
        if (!this.subCache.has(topic) && this.isActive()) {
            const subscription = this.nc.subscribe(topic, {
                callback: async (err, msg) => {
                    if (!err && fun) {
                        const result = fun(this.codec.decode(msg.data), msg);
                        if (msg.reply != null && msg.reply !== '') {
                            if (result instanceof Promise) {
                                this.nc.publish(msg.reply, this.encode(await result))
                            } else {
                                this.nc.publish(msg.reply, this.encode(result))
                            }
                        }
                    } else {
                        Message.error(`Error:${this.errResolver.resolver(err)}`);
                    }
                }
            });
            this.subCache.set(topic, fun);
            this.subscriptions.set(topic, subscription);
            this.tempCacheSubs.delete(topic);
        }
        if (!this.isActive()) {
            this.tempCacheSubs.set(topic, fun);
        }
    };

    unsub = (topic) => {
        if (this.subCache.has(topic) && this.subscriptions.has(topic)) {
            this.subscriptions.get(topic)
                .drain().catch(err => {
                Message.error(`Error:${this.errResolver.resolver(err)}`);
            });
            this.subCache.delete(topic)
            this.subscriptions.delete(topic);
            this.tempCacheSubs.delete(topic);
        }
    };

    pub = (topic, data) => {
        if (this.isActive()) {
            this.nc.publish(topic, this.encode(data))
        }
    }

    req = async (topic, data , timeout = 10 * 1000) => {
        if (this.isActive()) {
            return await this.nc.request(topic, this.encode(data), {
                timeout , noMux: true
            }).then(msg => {
                return this.codec.decode(msg.data)
            }).catch(err => {
                console.log(JSON.stringify(err))
                throw err;
            })
        }
    }

    encode = (data) => {
        if (data === null) {
            return '';
        }
        if (typeof data === 'object') {
            return this.codec.encode(data);
        } else {
            return this.sc.encode(data);
        }
    }

    decode = (data) => {
        if (data === null) {
            return '';
        }
        return this.sc.decode(data);
    }

    addConnectionListener = (listener) => {
        this.connectionListeners.push(listener)
    }


    close = async () => {
        if (this.isActive()) {
            this.subCache.clear();
            this.subscriptions.clear();
            this.tempCacheSubs.clear();
            return await this.nc.drain().catch(err => {
                Message.error(`Error:${this.errResolver.resolver(err)}`);
            });
        }
    }

    isActive = () => {
        if (this.state){
            switch (this.state){
                case Events.Disconnect:
                case Events.Reconnect:
                case DebugEvents.Reconnecting:
                    return false;
                case DebugEvents.PingTimer:
                    return true;
            }
        }
        return false;
    }
}

const natsClient = new Nats_client();


export {
    natsClient,
};